'use strict'
// 函数
/*
* 形式1
* function 函数名(形参列表){
*       函数体；
*       return 结果； 如果没有return语句，函数执行完毕后也会返回结果，只是结果为undefined。
* }
*
* 形式1 在这种方式下，function (x) { ... }是一个匿名函数，它没有函数名。这个匿名函数赋值给了变量，所以，通过变量名就可以调用该函数。
* let 变量名 = function(形参列表){
*       函数体；
*       return 结果； 如果没有return语句，函数执行完毕后也会返回结果，只是结果为undefined。
* };  注意在末尾这儿需要加一个;号，表示赋值语句的结束
*
* */
// function abs(x) {
//     if (x >= 0){
//         return x;
//     }else{
//         return -x;
//     }
// }

// let abs = function(x){
//     if (x >= 0){
//         return x;
//     }else{
//         return -x;
//     }
// }

// console.log(abs(-10));
//若直接像下面这样调用，则返回结果为NaN
// abs(); //此时abs(x)函数的参数x将收到undefined，计算结果为NaN。
//为了避免上面这种情况，对上述函数做出更改
let abs = function(x){
    if (typeof x !== 'number'){ //对参数进行检查
        throw 'Not a number';
    }

    //要想接收函数所有的传入参数，可以使用javaScript在函数内部特有的关键字arguments,也就是说即使函数不定义任何参数，还是可以拿到参数的值
    for (let i = 0; i < arguments.length;i++){
        console.log('arg' + i + '=' + arguments[i]);
    }

    //不管传入几个参数，但是形参还是只取对应的前面几个
    console.log(x);

    if (x >= 0){
        return x;
    }else{
        return -x;
    }

}
console.log(abs(10,20,30));

//实际上arguments最常用于判断传入参数的个数。
function foo(a,b,c) {
    if (arguments.length === 2){
        //实际拿到的参数是a和b，c为undefined
        c = b;
        b = null;
    }
    //......
}
foo(10,20,30);
foo(10,20);

//rest参数 --- 用来接收函数调用者传入的任意个参数里面的过剩参数
function exa(a,b,...rest) {
    // 有点像java里面的可变参数，rest参数只能写在最后，前面用...标识
    // 从运行结果可知，传入的参数先绑定a、b，多余的参数以数组形式交给变量rest
    console.log(a);
    console.log(b);
    console.log(rest); //[3,4,5,6,6,7,87,8]
}

exa(1,2,3,4,5,6,6,7,87,8);

// 定义一个计算圆面积的函数area_of_circle()
function area_of_circle(r,pi) {
    if (typeof pi === 'undefined'){ //这儿必须要加引号，因为typeof返回的是一个字符串值
        pi = 3.14;
    }
    return pi * r * r;
}

// 变量提升 --- 记住：javaScript引擎自动提升的是变量的声明，并不是提升的变量赋值
function foo2() {
    var x = 'Hello, ' + y;
    console.log(x);
    var y = 'Bob';
}

foo2(); // Hello, undefined  提升的是变量y的声明，故不会报错ReferenceError, 而不是提升变量y的赋值，故undefined
/*
* 由于JavaScript的这一怪异的“特性”，我们在函数内部定义变量时，请严格遵守“在函数内部首先申明所有变量”这一规则。
* 最常见的做法是用一个var申明函数内部用到的所有变量：
* */
function foo1() {
    var
        x = 1, // x初始化为1
        y = x + 1, // y初始化为2
        z, i; // z和i为undefined
    // 其他语句:
    for (i=0; i<100; i++) {
        // ...
    }
}

// 全局作用域 --- 不在任何函数内定义的变量就具有全局作用域。
// 实际上，JavaScript默认有一个全局对象window，全局作用域的变量实际上被绑定到window的一个属性
var course = 'Learn JavaScript';
console.log(course); // 'Learn JavaScript'
console.log(window.course); // 'Learn JavaScript'

//进一步大胆地猜测，我们每次直接调用的alert()函数其实也是window的一个变量
window.alert('调用window.alert()');
// 把alert保存到另一个变量:
let old_alert = window.alert;
// 给alert赋一个新函数:
window.alert = function () {} //实际上是动态的改变函数的行为

alert('无法用alert()显示了!');
old_alert('由old_alert接手了....');

// 恢复alert:
window.alert = old_alert;
alert('又可以用alert()了!');

/*
*   名字空间 全局变量会绑定到window上，不同的JavaScript文件如果使用了相同的全局变量，
    或者定义了相同名字的顶层函数，都会造成命名冲突，并且很难被发现。
    减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。
* */


// 唯一的全局变量MYAPP:
let MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

//局部作用域---由于JavaScript的变量作用域实际上是函数内部，我们在for循环等语句块中是无法定义具有局部作用域的变量的
function foo3() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然可以引用变量i
    console.log('测试i是否能访问到....')
    console.log(i); //200
}
foo3();
// 为了解决块级作用域，ES6引入了新的关键字let，用let替代var可以申明一个块级作用域的变量
function foo4() {
    let sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    // SyntaxError:
    // i += 1;
}
foo4(); //上面语法报错了

// 常量
const PI = 3.14;
// PI = 3; //常量的值是不能修改的
PI;

// 解构赋值---可以用来直接对多个变量同时赋值,也可以减少代码量
let [x,y,z] = ['hello','JavaScript','ES6'];
console.log(`x = ${x}, y = ${y}, z = ${z}`);

//如果数组本身还有嵌套，也可以通过下面的形式进行解构赋值，注意嵌套层次和位置要保持一致
let [l, [m, n]] = ['hello', ['JavaScript', 'ES6']];
console.log(`l = ${l}, m = ${m}, n = ${n}`);

//解构赋值还可以忽略某些元素
let [, , a] = ['hello', 'JavaScript', 'ES6'];

//如果需要从一个对象中取出若干属性，也可以使用解构赋值，便于快速获取对象的指定属性
let person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
let {name, age, passport} = person;
let {address:{city,zip}} = person;

// name, age, passport分别被赋值为对应属性:
console.log(`name = ${name}, age = ${age}, passport = ${passport}`);
// 使用解构赋值对对象属性进行赋值时，如果对应的属性不存在，变量将被赋值为undefined，这和引用一个不存在的属性获得undefined是一致的。
console.log(`city = ${city},zipcode = ${zip}`); //city = Beijing,zipcode = undefined

//如果要使用的变量名和属性名不一致，可以用下面的语法获取
let {passport:id} = person;
console.log(`id = ${id}`); //id = G-12345678

// 解构赋值还可以使用默认值，这样就避免了不存在的属性返回undefined的问题
let {name1, single=true} = person;
console.log(`single = ${single}`);

//有些时候，如果变量已经被声明了，再次赋值的时候，正确的写法也会报语法错误
// 声明变量:
let x, y;
// 解构赋值:
//{x, y} = { name: '小明', x: 100, y: 200}; //是因为JavaScript引擎把{开头的语句当作了块处理，于是=不再合法
// 语法错误: Uncaught SyntaxError: Unexpected token =
//解决办法
({x, y} = { name: '小明', x: 100, y: 200});
/*
* 解构赋值的用处
* 解构赋值在很多时候可以大大简化代码，减少代码量
* 快速获取当前页面的域名和路径
* 如果一个函数接收一个对象作为参数，那么，可以使用解构直接把对象的属性绑定到变量中
* */




